/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2013-2019.
 * Description: This file contains driver APIs to the rtos_fiq_glue subsystem.
 * Author: xiekunxun <xiekunxun@huawei.com>
 * Create: 2013-04-11
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/slab.h>
#if defined(CONFIG_ARM) && defined(CONFIG_ARCH_RTOS_HISI)
#include <mach/irqs.h>
#endif
#include "fiq_glue_private.h"
#include "fiq_routeway_register.h"

static char fiq_first_enter = FIQ_FIRST_REGISTER;
DEFINE_MUTEX(fiq_register_lock);

int request_fiq(void (*fiq_callback_func)(unsigned int), unsigned int fiq_num)
{
	int ret = 0;

#ifdef CONFIG_RTOS_HAL_FIQ_GLUE_EARLY
	if (g_fiq_early_mode == EARLY_FIQ_REGISTER_DONE) {
		g_fiq_early_mode = EARLY_FIQ_UNREGISTER;
		pr_info("early fiq callback unregisterd\n");
	}
#endif

	if (!fiq_callback_func) {
		pr_err("[%s]fiq callback function is NULL.\n", __func__);
		return -EINVAL;
	}
#if defined(CONFIG_ARM) && defined(CONFIG_ARCH_RTOS_HISI)
	if (fiq_num >= NR_IRQS) {
		pr_err("[%s]fiq num too large:%u.\n", __func__, fiq_num);
		return -EINVAL;
	}
#endif
	pr_devel("register fiq%u callback\n", fiq_num);
	mutex_lock(&fiq_register_lock);
	if (fiq_first_enter == FIQ_FIRST_REGISTER) {
		ret = fiq_first_register(fiq_callback_func, fiq_num);
		if (ret) {
			pr_err("[%s] fail to register fiq-no %u first func %pS\n", __func__, fiq_num, fiq_callback_func);
			goto out;
		}
		pr_info("[%s] succeed to register fiq-no %u  first func %pS\n", __func__, fiq_num, fiq_callback_func);
		/* Make sure this branch only be executed once */
		fiq_first_enter = FIQ_ALREADY_REGISTER;
	} else {
		ret = fiq_more_register(fiq_callback_func, fiq_num);
		if (ret) {
			pr_err("[%s] fail to register fiq-no %u  more func %pS\n", __func__, fiq_num, fiq_callback_func);
			goto out;
		}
		pr_info("[%s] succeed to register fiq-no %u more func %pS\n", __func__, fiq_num, fiq_callback_func);
	}
out:
	mutex_unlock(&fiq_register_lock);
	return ret;
}
EXPORT_SYMBOL(request_fiq);

int free_fiq(void (*fiq_callback_func)(unsigned int), unsigned int fiq_num)
{
	struct fiq_num_node *fiq_node = NULL;
	struct fiq_callback_node *callback_node = NULL;

	if (!fiq_callback_func) {
		pr_err("[%s]fiq callback function is NULL.\n", __func__);
		return -EINVAL;
	}

	mutex_lock(&fiq_register_lock);
	if (fiq_first_enter == FIQ_FIRST_REGISTER) {
		mutex_unlock(&fiq_register_lock);
		pr_warn("[%s]fiq callback nonregister.\n", __func__);
		return -EACCES;
	}

	fiq_node = fiq_find_node(fiq_num);
	if (!fiq_node) {
		pr_err("[%s]fiq register num %u is NULL.\n", __func__, fiq_num);
		goto error_inval;
	}

	callback_node = fiq_find_callback(fiq_node, fiq_callback_func);
	if (!callback_node) {
		pr_err("[%s] can not find func %pS\n", __func__, fiq_callback_func);
		goto error_inval;
	}

	list_del(&callback_node->node);
	kfree(callback_node);

	mutex_unlock(&fiq_register_lock);
	pr_info("[%s] success to deregister fiq-no %u func %pS\n", __func__, fiq_num, fiq_callback_func);
	return 0;

error_inval:
	mutex_unlock(&fiq_register_lock);
	return -EINVAL;
}
EXPORT_SYMBOL(free_fiq);

int free_fiq_all(unsigned int fiq_num)
{
	struct fiq_num_node *fiq_node = NULL;

	mutex_lock(&fiq_register_lock);
	if (fiq_first_enter == FIQ_FIRST_REGISTER) {
		mutex_unlock(&fiq_register_lock);
		pr_warn("[%s]fiq callback nonregister.\n", __func__);
		return -EACCES;
	}

	fiq_node = fiq_find_node(fiq_num);
	if (!fiq_node) {
		pr_err("[%s]fiq register num %u is NULL.\n", __func__, fiq_num);
		goto error_inval;
	}

	fiq_free_callback(fiq_node);

	mutex_unlock(&fiq_register_lock);
	pr_info("[%s] succee to deregister all func of fiq-no %u\n", __func__, fiq_num);
	return 0;

error_inval:
	mutex_unlock(&fiq_register_lock);
	return -EINVAL;
}
EXPORT_SYMBOL(free_fiq_all);

#ifdef CONFIG_RTOS_HAL_SET_IRQPRIORITY
bool check_fiq_num(int irq)
{
	int i;

	for (i = 0; i < g_fiq_glue_no_info.fiq_num; i++) {
		if (irq == g_fiq_glue_no_info.fiq_no[i])
			return true;
	}

	return false;
}
#endif

int hisi_request_fiq(void (*fiq_callback_func)(unsigned int))
{
	int ret;
	int i;

	pr_info("%s num is %u\n", __func__, g_fiq_glue_no_info.fiq_num);

	if (g_fiq_glue_no_info.fiq_num <= 0) {
		pr_err("fiq_nums must be configured greater than 0 if fiq is supported\n");
		return -EINVAL;
	}

	for (i = 0; i < g_fiq_glue_no_info.fiq_num; i++) {
		ret = request_fiq(fiq_callback_func, g_fiq_glue_no_info.fiq_no[i]);
		pr_info("%s %s to request the %d fiq %u\n", __func__,
			ret ? "fail" : "success", i, g_fiq_glue_no_info.fiq_no[i]);
		if (ret) {
			(void)hisi_free_fiq(fiq_callback_func);
			return -EINVAL;
		}
	}

#ifdef CONFIG_RTOS_HAL_FIQ_GLUE_EARLY
	if (g_fiq_early_mode == EARLY_FIQ_REGISTER_BEFORE) {
		g_fiq_early_mode = EARLY_FIQ_REGISTER_DONE;
		pr_info("early fiq callback registered\n");
	}
#endif

	return 0;
}
EXPORT_SYMBOL(hisi_request_fiq);

int hisi_free_fiq(void (*fiq_callback_func)(unsigned int))
{
	int ret;
	int err;
	int i;

	pr_info("%s num is %u\n", __func__, g_fiq_glue_no_info.fiq_num);

	if (g_fiq_glue_no_info.fiq_num <= 0) {
		pr_err("fiq_nums must be configured greater than 0 if fiq is supported\n");
		return -EINVAL;
	}

	ret = 0;
	for (i = 0; i < g_fiq_glue_no_info.fiq_num; i++) {
		err = free_fiq(fiq_callback_func, g_fiq_glue_no_info.fiq_no[i]);
		pr_info("%s %s to free the %d fiq %u\n", __func__,
			err ? "fail" : "success", i, g_fiq_glue_no_info.fiq_no[i]);
		if (err)
			ret = -EINVAL;
	}

	return ret;
}
EXPORT_SYMBOL(hisi_free_fiq);
